﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using Windows.Foundation;
using Windows.Foundation.Metadata;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.UI.Core;
using Windows.UI.Popups;
using EditoneLib;
using EditoneLibRT.Utils;
using NAudio.CoreAudioApi;
using NAudio.Wave;
using NAudio.Win8.Wave.WaveOutputs;

// ReSharper disable ExplicitCallerInfoArgument

namespace EditoneLibRT
{
    [Activatable(1)]
    public sealed class Project
    {
        private readonly EditoneLib.Project _project;
        private IList<Track> _tracks = new List<Track>();
        private WasapiOutRT _output;
        private PlaybackState _playbackState;
        private int _playbackMsec;

        internal Project(EditoneLib.Project project)
        {
            _project = project;
            project.CollectionChanged += ProjectOnCollectionChanged;
            if (project.Count > 0)
            {
                ProjectOnCollectionChanged(project, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }
            project.Settings.PropertyChanged += (sender, args) => { OnSettingsChanged(args.PropertyName); };
        }

        public event EventHandler<string> PropertyChanged;

        public event EventHandler<string> SettingsChanged;

        public int SampleRate
        {
            get { return EditoneLib.Settings.SampleRate; }
        }

        public int SampleSize
        {
            get { return EditoneLib.Settings.SampleSize; }
        }

        public IDictionary<string, dynamic> Settings
        {
            get { return (IDictionary<string, dynamic>)_project.Settings; }
        }

        public IList<Track> Tracks
        {
            get { return _tracks; }
            private set
            {
                if (Equals(value, _tracks)) return;
                _tracks = value;
                OnPropertyChanged();
            }
        }

        public string PlaybackState
        {
            get { return Enum.GetName(typeof(PlaybackState),_playbackState); }
            private set
            {
                var enumVal = Enum.Parse(typeof (PlaybackState), value);
                if (Equals(enumVal, _playbackState)) return;
                _playbackState = (PlaybackState) enumVal;
                OnPropertyChanged();
            }
        }

        public int PlaybackMsec
        {
            get { return _playbackMsec; }
            private set
            {
                if (Equals(value, _playbackMsec)) return;
                _playbackMsec = value;
                OnPropertyChanged();
            }
        }

        public static Project Create()
        {
            return new Project(new EditoneLib.Project());
        }

        public Track CreateTrack(string type)
        {
            return CreateTrack(type, -1);
        }

        public Track CreateTrack(string type, int index)
        {
            ITrack newTrack;
            switch (type)
            {
                case "MonoTrack":
                    newTrack = new MonoTrack();
                    break;

                case "StereoTrack":
                    newTrack = new StereoTrack();
                    break;

                default:
                    throw new KeyNotFoundException("This type of Track is unknown");
            }

            if (index == -1)
            {
                _project.Add(newTrack);
                return Tracks.Last();
            }

            _project.Insert(index, newTrack);
            return Tracks[index];
        }


        public IAsyncOperation<bool> ExportAsync()
        {
            return ((Func<Task<bool>>) (async () =>
            {
                var picker = new FileSavePicker();
                picker.FileTypeChoices.Add("Wave", new[] {".wav"});
                picker.FileTypeChoices.Add("MPEG-2 Audio Layer III", new[] {".mp3"});
                picker.FileTypeChoices.Add("Advanced Streaming Format (ASF)", new[] {".asf", ".wma"});
                picker.DefaultFileExtension = ".wav";
                var file = await picker.PickSaveFileAsync();
                
                if (file == null)
                {
                    return false;
                }

                using (var stream = _project.CreateStream())
                {
                    await Task.Run(() => { OutFileWriter.Export(file, stream); });
                }
                return true;
            }))().AsAsyncOperation();
        }

        public IAsyncOperation<IList<Clip>> ImportTracksAsync(long tickOffset)
        {
            return ((Func<Task<IList<Clip>>>) (async () =>
            {
                var picker = new FileOpenPicker() {FileTypeFilter = {".wav", ".mp3", ".wma", ".aac", ".adts", ".mp4"}};
                var files = await picker.PickMultipleFilesAsync();

                var clips = new List<Clip>();
                foreach (var storageFile in files)
                {
                    Clip clip;
                    try
                    {
                        clip = await Task.Run(() => Clip.Load(storageFile));
                    }
                    catch (ArgumentException e)
                    {
                        new MessageDialog("Error importing file", e.InnerException.Message).ShowAsync();
                        continue;
                    }
                    clip.ConvertAsync();

                    Track track;
                    switch (clip.LoadingStream.WaveFormat.Channels)
                    {
                        case 1:
                            track = CreateTrack("MonoTrack");
                            break;

                        case 2:
                            track = CreateTrack("StereoTrack");
                            break;

                        default:
                            continue;
                    }
                    track.StTrack.Name = Path.GetFileNameWithoutExtension(storageFile.Name);
                    track.AddRtClip(TimeSpan.FromTicks(tickOffset), clip);
                    clips.Add(clip);
                }
                return clips;
            }))().AsAsyncOperation();
        }

        public void Play(long tickOffset)
        {
            if (_playbackState == NAudio.Wave.PlaybackState.Playing || Tracks.Count == 0)
            {
                return;
            }
            _output = new WasapiOutRT(AudioClientShareMode.Shared, 100);
            var stream = _project.CreateStream();

            MappingMonoFix.Do(stream.Mapping);

            var dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;

            var waveStream = new RawSourceWaveStream(
                stream,
                WaveFormat.CreateIeeeFloatWaveFormat(EditoneLib.Settings.SampleRate, stream.Mapping.NumChannels)
                );

            waveStream.Position = tickOffset*EditoneLib.Settings.SampleRate*waveStream.BlockAlign/10000000;

            _output.Init(waveStream.ToSampleProvider());
            _output.PlaybackStopped += (sender, args) =>
            {
                dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                    (() =>
                    {
                        PlaybackState = Enum.GetName(typeof (PlaybackState), NAudio.Wave.PlaybackState.Stopped);
                    })
                );
            };
            stream.PositionChanged += (sender, args) =>
            {
                dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                       (() =>
                       {
                           PlaybackMsec = (int)(stream.Position / stream.Mapping.NumChannels / EditoneLib.Settings.SampleSize * 1000 / EditoneLib.Settings.SampleRate);
                       })
                   );
            };
            _output.Play();
            PlaybackState = Enum.GetName(typeof(PlaybackState), _output.PlaybackState);

        }
        
        public void Stop()
        {
            if (_output != null)
            {
                _output.Stop();
            }
        }

        public IAsyncAction Save(StorageFolder folder, string filename)
        {
            return Task.Run(() => new Serializer(folder, filename).Serialize(_project)).AsAsyncAction();
        }

        public IAsyncOperation<Project> Load(StorageFolder folder, string filename)
        {
            return Task.Run(() =>
            {
                var project = new Serializer(folder, filename).Deserialize();
                if (project == null)
                {
                    return null;
                }

                return new Project(project);
            }).AsAsyncOperation();
        }


        public bool RemoveTrack(Track track)
        {
            return _project.Remove(track.StTrack);
        }

        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, propertyName);
        }

        private void OnSettingsChanged(string settingName)
        {
            var handler = SettingsChanged;
            if (handler != null) handler(this, settingName);
        }

        private void ProjectOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Move)
            {
                var movingTrack = Tracks[e.OldStartingIndex];
                Tracks.Remove(movingTrack);
                Tracks.Insert(e.NewStartingIndex, movingTrack);
            }
            else if (e.Action == NotifyCollectionChangedAction.Reset)
            {
                Tracks = _project.Select((track => new Track(this, track))).ToList();
            }
            else
            {
                if (e.OldItems != null)
                {
                    foreach (var track in e.OldItems)
                    {
                        var targets = Tracks.Where((trackRt => track == trackRt.StTrack)).ToArray();
                        foreach (var toBeRemoved in targets)
                        {
                            Tracks.Remove(toBeRemoved);
                        }
                    }
                }

                if (e.NewItems != null)
                {
                    var index = e.NewStartingIndex;
                    foreach (var track in e.NewItems)
                    {
                        Tracks.Insert(index, new Track(this, (ITrack) track));
                        index++;
                    }
                }
            }
            OnPropertyChanged("Tracks");
        }
    }
}